fix(cli): scope skill installs to the agents in use, not all ~70 via --all#1748
Conversation
`hyperframes skills`, `skills update`, and `init` all shelled out to
`skills add ... --all`, which the upstream CLI expands to
`--skill '*' --agent '*' -y` — every skill into every one of the ~70 agent
conventions it knows about. A user who only runs Claude Code got skill
folders for Cursor, Codex, and 50+ tools they never touch.
Replace `--all` with a resolved target set (new resolveAgentTargets):
1. If the project already has agent skill folders (`.claude/skills`,
`.hermes/skills`, …), install ONLY to those — an existing folder is the
strongest signal of intent, so honour it exactly.
2. Otherwise (blank project):
a. Running under Claude Code (CLAUDECODE) → just claude-code.
b. Else probe PATH for installed agent CLIs (claude, hermes, droid,
cursor, codex, opencode, gemini) — the gstack approach.
c. Else fall back to claude-code + the shared `.agents` universal dir,
which Cursor, Codex, OpenCode, Gemini, Copilot and ~14 others read
from in project scope. Never `--agent '*'`.
Installs stay `--skill '*'` (all skills) + `--copy` (faithful, detectable by
`skills check`); only the agent fan-out is scoped. The dir<->key map is
explicit because dir names differ from upstream keys (`.factory`/droid,
`.hermes`/hermes-agent) and many agents share `.agents` (-> the single
`universal` key). Keys verified against vercel-labs/skills@v1.5.13.
Verified end-to-end in an isolated project + HOME: the new args land exactly
`.claude/skills` (19) + `.agents/skills` (19) and nothing else — 2 folders,
not 50+. Pure resolver covered by skillsTargets.test.ts; command-arg shape and
the "never --all" regression pinned in skills.test.ts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jrusso1020
left a comment
There was a problem hiding this comment.
LGTM — no blockers. Nicely-scoped robustness fix, and it directly closes the "handle all agents like skills add / gstack" concern raised on #1738. Verified independently:
- Upstream flags valid at the pinned
vercel-labs/skills@v1.5.13—-a, --agent,-s, --skill,-y, --yesall exist (src/cli.ts), and--allis literally shorthand for--skill '*' --agent '*' -y. So replacing--allwith--skill '*' --agent <resolved> --yesis a correct narrowing, not a behavior guess. resolveAgentTargetsnever resolves to empty — existing project skill folders (honor intent) →CLAUDECODE→ PATH-probe of installed agent CLIs (the gstack approach) → floor (claude-code+.agentsuniversal). The floor fallback is the key safety net so a blank machine still gets a sane target.DIR_TO_KEYkeys verified against v1.5.13; the explicit dir↔key mapping (.factory↔droid,.hermes↔hermes-agent) and the OpenClaw exclusion (bareskills/collision) are thoughtful.- Composes correctly with #1744's
--copy—runSkillsAddstill appends--copyafter the resolved args; explicitextraArgsstill wins for callers/tests; the priorinstallAllSkills({extraArgs:['--all','--yes']})caller is updated to use resolution.
Good coverage (skillsTargets.test.ts +130 + skills.test.ts), and the Installing to: … log is nice transparency.
— Jerrai
miga-heygen
left a comment
There was a problem hiding this comment.
Review — fix(cli): scope skill installs to relevant agents
SHA: 7d28f29
Verdict: LGTM — clean, well-reasoned change. A few observations below, nothing blocking.
What this does (my read)
The PR replaces every --all (= --skill '*' --agent '*' -y) occurrence in the skills install pipeline with a resolver (resolveAgentTargets) that picks only the agents the project or machine actually uses. The resolver lives in a new skillsTargets.ts utility with a clear priority chain: existing project folders > CLAUDECODE env > PATH-detected CLIs > a claude-code + universal floor. The buildSkillsAddArgs helper produces --skill '*' --agent <targets...> --yes — verified against the upstream parseAddOptions which consumes variadic args after --agent until it hits another flag.
Correctness
-
DIR_TO_KEY mapping — verified against
vercel-labs/skills@v1.5.13agents.ts:.claude->claude-code,.agents->universal,.hermes->hermes-agent,.factory->droid,.kiro->kiro-cli. All correct. -
Upstream
--agentparsing — the upstreamparseAddOptionsgrabs variadic non-flag args after--agent, so--agent claude-code universal --yesparses asagent: ["claude-code", "universal"], yes: true. The generated arg order frombuildSkillsAddArgsis correct. -
init.tscleanup — the old code had aCLAUDECODEbranch that passed["--all", "--agent", "claude-code", "--yes"]and a fallback of["--all", "--yes"]. The new code delegates entirely toresolveAgentTargets, which handles theCLAUDECODEcase (step 2a) and applies the same logicinitwas hand-rolling. Good simplification — the special-casing was duplicated between init and skills, now it's in one place. -
skills updatepath — previously passed["--all", "--yes"]asextraArgs, now passes noextraArgssorunSkillsAddresolves targets itself. TheinstallAllSkills→runSkillsAddchain correctly falls through toresolveAgentTargetswhenopts.extraArgsis undefined. Clean. -
extraArgsescape hatch —runSkillsAddstill respects an explicitextraArgswhen passed, so callers that need specific targeting can bypass the resolver. Good for forward compatibility.
Observations (non-blocking)
-
isDiris slightly over-cautious — it calls bothexistsSyncandstatSync, butstatSyncalready throwsENOENTwhen the path doesn't exist, so theexistsSynccheck is redundant. Thetry/catcharound both handles it fine though, so this is just a nit — no functional impact. -
PATH probe creates empty files as "executables" in the test helper (
pathWith) — works on Linux/macOS becauseexistsSyncdoesn't check the execute bit. On Windows the test also probes.exe/.cmd/.batextensions, but the test helper doesn't create those suffixed files. This is fine because the tests run on Linux and the Windows path is an unlikely CI target, but worth noting if you ever add Windows test matrix. -
The
DETECTABLElist is conservative — only 7 CLIs are probed. Agents likegoose,continue,kilo,devin, etc. are omitted. This seems intentional (the comment says "the gstack approach"), and theuniversalfloor covers agents that read.agents/skillsanyway. Just flagging for awareness — if a user has onlygooseinstalled, they'd get theclaude-code + universalfloor rather than thegoosekey. Sincegooseuses.goose/skills(not.agents/skills), the universal bucket wouldn't reach it. This is an edge case and arguably the right call for now — expanding the list can be a follow-up. -
keysForDirsiteration order — relies onObject.entries(DIR_TO_KEY)preserving insertion order (spec-guaranteed for string keys in V8/modern engines). The comment "claude-code first" in the test matches the object literal order. Solid. -
Test coverage — good coverage of all resolver branches: existing folders, dir-to-key mapping,
CLAUDECODEenv, PATH detection, universal-bucket collapse, floor fallback, the "never*" invariant, andbuildSkillsAddArgsshape. Theskills.test.tsadditions pin the spawn args and add a "never--all" regression guard. The mock ofresolveAgentTargetsinskills.test.tsis the right call — isolates command-arg construction from environment-dependent resolution.
CI
Several checks still pending/skipping at time of review (Typecheck, Build, CLI smoke, Test, etc.). No failures observed.
Overall this is a well-structured change with clear reasoning, good test coverage, and correct integration with the upstream CLI. Ship it.
— Miga
miguel-heygen
left a comment
There was a problem hiding this comment.
Reviewed at 7d28f291. No blockers.
What I verified:
packages/cli/src/commands/skills.ts:57keepsrunSkillsAddas the single install chokepoint and replaces the default--allspray with concrete--skill "*" --agent <resolved> --yesargs while preserving explicitextraArgsfor callers that intentionally bypass resolution.packages/cli/src/utils/skillsTargets.ts:128resolves targets in the right order: existing project skill folders first, thenCLAUDECODE, then PATH-detected installed agent CLIs, then theclaude-code + universalfloor, so it never emits the*wildcard or an empty target set.- Upstream
vercel-labs/skills@v1.5.13accepts the generated argv shape:parseAddOptionsconsumes multiple non-flag values after--agent, and the mapped keys (claude-code,universal,hermes-agent,droid,kiro-cli) match upstreamagents.ts. packages/cli/src/utils/skillsTargets.test.ts:33pluspackages/cli/src/commands/skills.test.ts:200cover the resolver branches and pin the no---all/ no-wildcard regression.
Additive to Rames + Miga: I do not see a blocking gap. Their notes on isDir, the conservative detectable CLI list, and Windows-suffixed test helpers are non-blocking.
CI note: all completed checks were green at review time; Tests on windows-latest was still pending, so merge should wait for that check if required.
Verdict: APPROVE
Reasoning: This narrows the install target set without changing the upstream install contract, and the resolver has a safe floor plus focused tests for the no-spray behavior.
— Magi
Resolve the skills-distribution conflicts. main already carried the manifest/version-check + prune work (#1738/#1740/#1743) and the #1748 "scope installs via skillsTargets" approach; this branch reworks install to the global symlink-mirror model, so: - Keep main's prune/removed-detection (skillsManifest.ts, the rm + update prune path, the related tests) verbatim; re-apply only the global-first locateInstall on top. - Replace #1748's skillsTargets-based scoped install with the global --copy + --full-depth install + mirrorGlobalSkills fan-out; delete the now-superseded skillsTargets.ts (+ test). - Keep main's 0.7.13 version bump + add the gen:agent-dirs script. - Regenerate skills-manifest.json against the merged skills/ tree. Verified: typecheck, oxlint, oxfmt, fallow gate all clean; 1044 CLI tests pass (main's prune suite + the new mirror/global suite together).
What
hyperframes skills,hyperframes skills update, andhyperframes initno longer pass--allto the upstreamskills add. A newresolveAgentTargets(packages/cli/src/utils/skillsTargets.ts) decides which agents an install targets, andrunSkillsAdd— the single chokepoint all three paths funnel through (installAllSkills→runSkillsAdd) — builds--skill '*' --agent <resolved…> --yesfrom it.Effect: an install lands in the agent(s) the project (or machine) actually uses, instead of all ~70 agent conventions the upstream CLI knows about.
Why
--allis upstream shorthand for--skill '*' --agent '*' -y— every skill into every one of the ~70 agent conventions. A user who only runs Claude Code got skill folders for Cursor, Codex, and 50+ tools they never touch: noise in the project, and skills written to directories nothing reads.This is the targeting complement to the recent skills work: detection is sound (#1738), update reconciles removals (#1740 / #1743), installs are byte-faithful and correctly placed (#1744
--copy) — but the agent fan-out was still universal. This scopes it.How — how we pick the target agents
resolveAgentTargets, in priority order:.claude/skills,.hermes/skills, …) → install only to those. An existing folder is the strongest signal of intent, so we honour it exactly and add nothing else.CLAUDECODE) → justclaude-code.PATHfor installed agent CLIs (claude,hermes,droid,cursor,codex,opencode,gemini) — an executable on PATH means that agent is actually installed here.claude-code+ the shared.agentsuniversal dir, which Cursor, Codex, OpenCode, Gemini, Copilot and ~14 others read from in project scope. Never--agent '*'.The dir↔key map is explicit because host-dir names differ from upstream agent keys (
.factory↔droid,.hermes↔hermes-agent) and many agents share.agents(→ the singleuniversalkey). Keys verified againstvercel-labs/skills@v1.5.13.--skill '*'(all skills) and--copy(faithful,skills check-detectable — #1744) are unchanged; only the agent fan-out is scoped. The install prints its chosen targets and the reason.Test plan
Unit — pure resolver (
skillsTargets.test.ts): every branch (existing folders → keys,.factory→droid/.kiro→kiro-cli/.agents→universalmapping,CLAUDECODE, PATH detection, universal-bucket collapse, the.claude+.agentsfloor), existing-folders-beat-env precedence, and a "never returns'*'" invariant.buildSkillsAddArgsshape pinned.Unit — command args (
skills.test.ts): the spawn builds--skill '*' --agent … --yes --copy, never--all, and the value after--agentis never the'*'wildcard (regression guard). Existing GIT_CLONE_PROTECTION / prune-scope / update-exit-code tests still pass.End-to-end (isolated project + HOME, real upstream
skills@1.5.13,--copy):.claude/skills(19) +.agents/skills(19).hermes/skillspre-seeded.hermes/skills(19) only — no.claude, no.agentsFull CLI suite green (
bunx vitest run packages/cli— 1030 pass; the 2*.browser.test.tserrors are a localhappy-domresolution quirk under ad-hoc vitest, unrelated).oxlint,oxfmt --check,tsc, andfallowall clean (lefthook pre-commit passed).Docs follow-up (separate):
README.md,docs/quickstart.mdx,docs/guides/prompting.mdx, anddocs/guides/skills.mdxstill tell users to runnpx skills add heygen-com/hyperframes --alldirectly. That's the raw upstream CLI path (it bypasseshyperframes), so it still sprays — worth updating in its own change.🤖 Generated with Claude Code